import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import org.lwjgl.*;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.*;
import org.lwjgl.util.Color;
import org.lwjgl.util.glu.GLU;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL20.*;

public class GLTestingAgain
{

	static int realPathIterator = 0;
	static float shit, shack;
	static float[] red = { 1, 0, 0 };
	static float[] blue = { 0, 0, 1 };
	static float[] green = { 0, 1, 0 };
	static float[] yellow = { 1, 1, 0 };
	static float[] white = { 1, 1, 1 };
	static ByteBuffer shitState = ByteBuffer.allocateDirect(128);
	static DoubleBuffer lastState = shitState.asDoubleBuffer();
	static float n = 0;
	static double piover180 = Math.PI / 180;

	static double[] sineTable = buildSineTable();
	static double[] cosineTable = buildCosineTable();

	static double scalex, scaley, X, Y, Z;
	static float pitch, yaw, roll;
	
	static double[] zpos = {0,0,1};
	static double[] zneg = {0,0,-1};
	static double[] xpos = {1,0,0};
	static double[] xneg = {-1,0,0};
;

	static ArrayList<double[]> theRealPath = buildPath();

	// None of these 'RotateLocalYaw/Pitch/Roll' functions work. Ignore them.
	static void rotateLocalYaw(int whichway)
	{
		if (whichway > 0)
		{
			pitch += (float) Math.sin(piover180 * roll);
			yaw += (float) Math.cos(piover180 * pitch);
			roll += (float) Math.sin(piover180 * pitch);
		} else
		{
			pitch -= (float) Math.sin(piover180 * roll);
			yaw -= (float) Math.cos(piover180 * pitch);
			roll -= (float) Math.sin(piover180 * pitch);
		}
		// glRotatef(angle, (float)Math.sin(piover180*roll) , (float)
		// Math.cos(piover180*pitch) ,(float) Math.sin(piover180*pitch));
	}

	static void rotateLocalPitch(int whichway)
	{
		if (whichway > 0)
		{
			pitch += (float) Math.cos(piover180 * yaw);
			yaw += (float) Math.sin(piover180 * roll);
			roll += (float) Math.sin(piover180 * yaw);
		} else
		{
			pitch -= (float) Math.cos(piover180 * yaw);
			yaw -= (float) Math.sin(piover180 * roll);
			roll -= (float) Math.sin(piover180 * yaw);
		}
		// glRotatef(angle, (float)Math.cos(piover180*yaw) , (float)
		// Math.sin(piover180*roll) ,(float) Math.sin(piover180*yaw));

	}

	static void rotateLocalRoll(int w)
	{
		if (w > 0)
		{
			pitch += (float) Math.sin(piover180 * yaw) * -1;
			yaw += (float) Math.sin(piover180 * pitch);
			roll += (float) Math.cos(piover180 * yaw);
		} else
		{
			pitch -= (float) Math.sin(piover180 * yaw) * -1;
			yaw -= (float) Math.sin(piover180 * pitch);
			roll -= (float) Math.cos(piover180 * yaw);
		}
		// glRotatef(angle, (float)Math.sin(piover180*yaw)*-1 , (float)
		// Math.sin(piover180*pitch) ,(float) Math.cos(piover180*yaw));
	}

	public static void main(String[] args)
	{

		try
		{

			Display.setDisplayModeAndFullscreen(new DisplayMode(1280, 1024));
			Display.create();

		} catch (Exception e)
		{
		}

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 2000.0);

		glMatrixMode(GL_MODELVIEW);
		glViewport(0, 0, 1280, 1024);

		while (!Display.isCloseRequested())
		{

			try
			{
				display();
			} catch (Exception e)
			{
				e.printStackTrace();
				System.exit(1);
			}

			// You see this line?
			Display.update();
			// Don't you DARE remove it!
			// Doing so will lock up the entire machine!

		}
		Display.destroy();

	}
	
	
	static ArrayList<double[]> buildPath()
	{
		ArrayList<double[]> path = new ArrayList<double[]>();

		path.addAll(buildStraightPiece(1, new double[] { -5, 0, 0 }, new double[] { 0, 0, 1 }));
		path.addAll(buildStraightPiece(2, new double[] { -5, 0, 1 }, new double[] { 0, 1, 1 }));
		path.addAll(buildStraightPiece(1, new double[] { -5, 2, 3 }, new double[] { 0, -1, 1 }));
		path.addAll(buildStraightPiece(1, new double[] { -5, 1, 4 }, new double[] { 1, 0, 0 }));
		path.addAll(buildStraightPiece(1, new double[] { -4, 1, 4 }, new double[] { 0, 0, 1 }));
		path.addAll(buildStraightPiece(1, new double[] { -4, 1, 5 }, new double[] { 1, 0, 0 }));
		
		path.addAll(buildCurvedPiece(2, new double[]{-3,1,5}, xpos, zneg, 60));
		
		//path.addAll(buildSpiral(1, new double[] { -4, 1, 5 }, new double[] { 1, 0, 0 }));

		return path;
	}

	static void drawPath(ArrayList<double[]> path, float[] color)
	{
		glColor3f(color[0], color[1], color[2]);
		glBegin(GL_LINE_STRIP);
		for (double[] p : path)
		{		
			glVertex3d(p[0], p[1], p[2]);
		}
		glEnd();
	}

	/*
	 * This will create 4 cubes of different colors surrounding the camera. It
	 * properly handles the movement of the camera in the XZ plane, that is, by
	 * only using the arrow keys. You can change pitch with the 1 and 4 keys,
	 * and you can change roll with the X and Z keys. But doing this will
	 * severely fuck everything up from then on out. Sorry.
	 */
	static void display() throws InterruptedException
	{
		glMatrixMode(GL_MODELVIEW);
		glClear(GL_COLOR_BUFFER_BIT);
		glLoadIdentity();

		if (Mouse.isButtonDown(0))
		{
			X = (Mouse.getX() - Display.getDisplayMode().getWidth() / 2.0) / 100.0;
			Y = (Mouse.getY() - Display.getDisplayMode().getWidth() / 2.0) / 100.0;

		}
		if (Mouse.isButtonDown(2))
		{
			scalex = Mouse.getX() / 200.0;
			scaley = Mouse.getY() / 200.0;
		}

		// Does not handle roll!
		if (Keyboard.isKeyDown(Keyboard.KEY_UP))
		{

			Z += .5 * Math.cos(piover180 * yaw);
			Y += .5 * Math.sin(piover180 * pitch);
			X -= .5 * Math.sin(piover180 * yaw);

		}
		// Does not handle roll!
		if (Keyboard.isKeyDown(Keyboard.KEY_DOWN))
		{
			Z -= .5 * Math.cos(piover180 * yaw);
			Y -= .5 * Math.sin(piover180 * pitch);
			X += .5 * Math.sin(piover180 * yaw);

		}

		glGetDouble(GL_MODELVIEW_MATRIX, lastState);

		if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
		{
			yaw++;
			// System.out.println(yaw+"y");
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_LEFT))
		{
			yaw--;
			// System.out.println(yaw+"y");
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_1))
		{
			pitch++;
			// System.out.println(pitch+"p");
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_4))
		{
			pitch--;
			// System.out.println(pitch+"p");
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_X))
		{
			roll += 1;
		}
		if (Keyboard.isKeyDown(Keyboard.KEY_Z))
		{
			roll -= 1;
		}

		// drawCrosshair(white);

		glRotatef(pitch, 1, 0, 0);
		glRotatef(yaw, 0, 1, 0);
		glRotatef(roll, 0, 0, 1);

		double[] c = theRealPath.get(realPathIterator);
		glTranslated(X, Y, Z);

		// This is important otherwise your motion is too fast.
		Thread.sleep(10);

		drawCube(0, 0, -4, red);
		drawCube(4, 0, 0, blue);
		drawCube(0, 0, 4, green);
		drawCube(-4, 0, 0, yellow);

		// IF (MOVING) THEN
		drawCube(0, shit, 0, white);
		// ENDIF

		shit += (Math.sin((shack += 10) / 90.0));
		if (shack > 32400)
		{
			shack = 1;
		}

		drawSineWave();
		drawCosineWave();
		drawPath(theRealPath, green);
		// drawCubev(theRealPath.get(realPathIterator), new double[] {
		// cosine(realPathIterator / 100.0), cosine(realPathIterator / 100.0), 1
		// }, sine(realPathIterator / 8.0) * 30.0);
		// realPathIterator += 4;
		// if (realPathIterator >= theRealPath.size())
		// realPathIterator = 0;

		glFlush();
	}

	static void drawCrosshair(float color[])
	{
		glColor3f(color[0], color[1], color[2]);
		glBegin(GL_LINE_STRIP);
		glVertex3f(0f, 0f, -1.5f);

		glVertex3f(1f, 0f, -1.5f);
		glColor3f(0, 1, 0);
		glVertex3f(-1f, 0f, -1.5f);
		// glVertex3f(0f,0,-2);
		glEnd();
	}

	static void drawSineWave()
	{

		glColor3f(0.0f, 0f, 1.0f);
		glBegin(GL_LINE_STRIP);
		for (int i = 0; i < 36000; i += 1)
		{
			glVertex3d(i / 3600.0, sineTable[i], 0);

		}
		glEnd();
	}

	static void drawCosineWave()
	{
		glColor3f(0.0f, .5f, 0);
		glBegin(GL_LINE_STRIP);
		for (int i = 0; i < 36000; i += 1)
		{
			glVertex3d(i / 3600.0, cosineTable[i], 0);

		}
		glEnd();
	}
	
	static void drawGrid(int x, int y , int z, double resolution)
	{
		for(int i = x*-1; i < x; i++)
		{
			for (int j = y*-1; j< y; j++)
			{			
				for (int k = z*-1; k<z;j++)
				{
					 
				}
			}
		}
	}

	// This will draw a wireframe cube of a fixed size at the location (x,y,z)
	// with color {red, green, blue}
	static void drawCubed(double x, double y, double z, float[] color)
	{
		glColor3f(color[0], color[1], color[2]);
		glBegin(GL_LINE_STRIP);
		glVertex3d(0 + x, 0 + y, 0 + z);
		glVertex3d(1 + x, 0 + y, 0 + z);
		glVertex3d(1 + x, 1 + y, 0 + z);
		glVertex3d(0 + x, 1 + y, 0 + z);

		glVertex3d(0 + x, 1 + y, -1 + z);
		glVertex3d(0 + x, 0 + y, -1 + z);
		glVertex3d(1 + x, 0 + y, -1 + z);
		glVertex3d(1 + x, 1 + y, -1 + z);
		glVertex3d(0 + x, 1 + y, -1 + z);

		glVertex3d(0 + x, 1 + y, 0 + z);
		glVertex3d(0 + x, 0 + y, 0 + z);
		glVertex3d(0 + x, 0 + y, -1 + z);
		glVertex3d(1 + x, 0 + y, -1 + z);
		glVertex3d(1 + x, 0 + y, 0 + z);
		glVertex3d(1 + x, 1 + y, 0 + z);
		glVertex3d(1 + x, 1 + y, -1 + z);
		glEnd();
	}

	static double sine(double angle)
	{
		return sineTable[(int) ((angle % 360) * 100)];
	}

	static double cosine(double angle)
	{
		return cosineTable[(int) ((angle % 360) * 100)];
	}

	static ArrayList<double[]> buildStraightPiece(double length, double[] location, double[] directionVector)
	{
		ArrayList<double[]> piece = new ArrayList<double[]>();
		int steps = (int) length * 100;

		for (int i = 0; i < steps; i++)
		{
			double[] point = new double[] { i * .01 * directionVector[0] + location[0], i * .01 * directionVector[1] + location[1], i * .01 * directionVector[2] + location[2] };
			piece.add(point);
		}

		return piece;
	}

	static ArrayList<double[]> buildSpiral(double l, double[] loc, double[] dvector)
	{
		ArrayList<double[]> piece = new ArrayList<double[]>();

		int steps = (int) l * 100000;

		double angularstep = 3600.0 / steps;

		for (int i = 0; i < steps; i++)
		{
			double[] point = new double[] { i * .01 * cosine(angularstep * i) + loc[0], loc[1] + 6 * sine(i * 3), i * .01 * sine(angularstep * i) + loc[2] };
			piece.add(point);
		}
		return piece;
	}

	static ArrayList<double[]> buildCurvedPiece(double l, double[] loc, double[] fd, double[] sd, double angle)
	{
		ArrayList<double[]> piece = new ArrayList<double[]>();

		int steps = (int) l * 100;

		double angularstep = angle / steps;

		if (fd[2] == 1 && sd[0] == 1)
		{
			for (int i = 0; i < steps; i++)
			{// +Z to +X
				double[] point = new double[] { -cosine(i * angularstep) * l + loc[0] + l, loc[1], sine(angularstep * i) * l + loc[2] };
				piece.add(point);
			}
		} else if (fd[2] == 1 && sd[0] == -1)
		{
			for (int i = 0; i < steps; i++)
			{// +Z to -X
				double[] point = new double[] { cosine(i * angularstep) * l + loc[0] - l, loc[1], sine(angularstep * i) * l + loc[2] };
				piece.add(point);
			}
		} 
		else if (fd[2] == -1 && sd[0] == -1)
		{
			 for (int i = 0; i < steps; i++)
			 {//-Z to -X
			 double[] point = new double[] { cosine(i*angularstep)*l+loc[0]
			 -l, loc[1], -sine(angularstep * i)*l + loc[2] };
			 piece.add(point);
			 }
		} 
		else if (fd[2] == -1 && sd[0] == 1)
		{
			 for (int i = 0; i < steps; i++)
			 {//-Z to +X
			 double[] point = new double[] { -cosine(i*angularstep)*l+loc[0]
			 +l, loc[1], -sine(angularstep * i)*l + loc[2] };
			 piece.add(point);
			 }
					
		}
		else if(fd[0] == 1 && sd[2] == 1){
		 for (int i = 0; i < steps; i++)
		 { //+X to Z+
		 double[] point = new double[] { sine(i*angularstep)*l+loc[0] ,
		 loc[1], -cosine(angularstep * i)*l + loc[2] +l };
		 piece.add(point);
		 }
		}
		else if(fd[0] == 1 && sd[2] == -1){
		 for (int i = 0; i < steps; i++)
		 { //+X to Z-
		 double[] point = new double[] { sine(i*angularstep)*l+loc[0] ,
		 loc[1], cosine(angularstep * i)*l + loc[2] -l };
		 piece.add(point);
		 }
		}
		else if(fd[0] == -1 && sd[2] == -1){
		 for (int i = 0; i < steps; i++)
		 { // X- to Z-
		 double[] point = new double[] { -sine(i*angularstep)*l+loc[0] ,
		 loc[1], cosine(angularstep * i)*l + loc[2] -l };
		 piece.add(point);
		 }
		}
		else if(fd[0] == -1 && sd[2] == 1){
		 for (int i = 0; i < steps; i++)
		 { // X- to Z+
		 double[] point = new double[] { -sine(i*angularstep)*l+loc[0] ,
		 loc[1], l*-cosine(angularstep * i) + loc[2] +l };
		 piece.add(point);
		 }
		}

		return piece;
	}



	static void drawCube(float x, float y, float z, float[] color)
	{
		glColor3f(color[0], color[1], color[2]);
		glBegin(GL_LINE_STRIP);
		glVertex3f(0 + x, 0 + y, 0 + z);
		glVertex3f(1 + x, 0 + y, 0 + z);
		glVertex3f(1 + x, 1 + y, 0 + z);
		glVertex3f(0 + x, 1 + y, 0 + z);

		glVertex3f(0 + x, 1 + y, -1 + z);
		glVertex3f(0 + x, 0 + y, -1 + z);
		glVertex3f(1 + x, 0 + y, -1 + z);
		glVertex3f(1 + x, 1 + y, -1 + z);
		glVertex3f(0 + x, 1 + y, -1 + z);

		glVertex3f(0 + x, 1 + y, 0 + z);
		glVertex3f(0 + x, 0 + y, 0 + z);
		glVertex3f(0 + x, 0 + y, -1 + z);
		glVertex3f(1 + x, 0 + y, -1 + z);
		glVertex3f(1 + x, 0 + y, 0 + z);
		glVertex3f(1 + x, 1 + y, 0 + z);
		glVertex3f(1 + x, 1 + y, -1 + z);
		glEnd();

	}

	static void drawCubev(double[] xyz, double[] color, double scale)
	{
		glColor3d(color[0], color[1], color[2]);
		glBegin(GL_LINE_STRIP);
		glVertex3d(0 + xyz[0], 0 + xyz[1], 0 + xyz[2]);
		glVertex3d(scale + xyz[0], 0 + xyz[1], 0 + xyz[2]);
		glVertex3d(scale + xyz[0], scale + xyz[1], 0 + xyz[2]);
		glVertex3d(0 + xyz[0], scale + xyz[1], 0 + xyz[2]);

		glVertex3d(0 + xyz[0], scale + xyz[1], -scale + xyz[2]);
		glVertex3d(0 + xyz[0], 0 + xyz[1], -scale + xyz[2]);
		glVertex3d(scale + xyz[0], 0 + xyz[1], -scale + xyz[2]);
		glVertex3d(scale + xyz[0], scale + xyz[1], -scale + xyz[2]);
		glVertex3d(0 + xyz[0], scale + xyz[1], -scale + xyz[2]);

		glVertex3d(0 + xyz[0], scale + xyz[1], 0 + xyz[2]);
		glVertex3d(0 + xyz[0], 0 + xyz[1], 0 + xyz[2]);
		glVertex3d(0 + xyz[0], 0 + xyz[1], -scale + xyz[2]);
		glVertex3d(scale + xyz[0], 0 + xyz[1], -scale + xyz[2]);
		glVertex3d(scale + xyz[0], 0 + xyz[1], 0 + xyz[2]);
		glVertex3d(scale + xyz[0], scale + xyz[1], 0 + xyz[2]);
		glVertex3d(scale + xyz[0], scale + xyz[1], -scale + xyz[2]);
		glEnd();

	}

	/*
	 * This is an example of some code to handle keyboard and mouse input.
	 */
	public static void pollInput()
	{

		if (Mouse.isButtonDown(0))
		{
			int x = Mouse.getX();
			int y = Mouse.getY();

			System.out.println("MOUSE DOWN @ X: " + x + " Y: " + y);
		}

		if (Keyboard.isKeyDown(Keyboard.KEY_SPACE))
		{
			System.out.println("SPACE KEY IS DOWN");
		}

		while (Keyboard.next())
		{
			if (Keyboard.getEventKeyState())
			{
				if (Keyboard.getEventKey() == Keyboard.KEY_A)
				{
					System.out.println("A Key Pressed");
				}
				if (Keyboard.getEventKey() == Keyboard.KEY_S)
				{
					System.out.println("S Key Pressed");
				}
				if (Keyboard.getEventKey() == Keyboard.KEY_D)
				{
					System.out.println("D Key Pressed");
				}
			} else
			{
				if (Keyboard.getEventKey() == Keyboard.KEY_A)
				{
					System.out.println("A Key Released");
				}
				if (Keyboard.getEventKey() == Keyboard.KEY_S)
				{
					System.out.println("S Key Released");
				}
				if (Keyboard.getEventKey() == Keyboard.KEY_D)
				{
					System.out.println("D Key Released");
				}
			}
		}
	}


	/*
	 * Down here is some junk code. I thought ahead of time that it may improve
	 * performance if instead of computing trig functions over and over every
	 * time we need to know the angle of the track upon which the cart sits, we
	 * could just look the values up in huge tables. This probably won't be
	 * necessary, though.
	 */

	public static double[] buildSineTable()
	{
		double piover180 = (Math.PI / 180.0);
		double[] Table = new double[36001];
		int intdegree = 0;
		for (double degree = 0; degree < 360; degree += 0.01)
		{


			Table[intdegree] = Math.sin(piover180 * degree);

			intdegree++;
		}
		return Table;
	}

	public static double[] buildCosineTable()
	{
		double piover180 = (Math.PI / 180.0);
		double[] cosineTable = new double[36001];
		int intdegree = 0;
		for (double degree = 0; degree < 360; degree += 0.01)
		{

			cosineTable[intdegree] = Math.cos(piover180 * degree);
			intdegree++;

		}
		return cosineTable;
	}

	/*
	 * This is probably a good idea. Use a local spherical coordinate system for
	 * the cart, and then convert it to the absolute cartesian system that
	 * OpenGL uses. I haven't tried to implement this yet.
	 */


}
